package com.zk.config.web.controller;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import com.google.gson.Gson;
import com.zk.config.api.util.ComUtil;
import com.zk.config.api.util.ZkCom;
import com.zk.config.api.util.ZkOperate;
import com.zk.config.web.constants.Constants;
import com.zk.config.web.model.Response;
import com.zk.config.web.op.MyZkClient;
import com.zk.config.web.op.Zk;
import com.zk.config.web.util.CompressUtil;
import com.zk.config.web.util.ZkCacheUtil;
import lombok.Setter;

@Controller
@RequestMapping("/op")
public class ZkOpController {
	private static final Logger logger = LoggerFactory.getLogger(ZkOpController.class);
	private static final String SEPARATOR = "/";
	private Gson gson = new Gson();
	@Setter
	@Value("${zookeeper.backup.directory}")
	private String backupDirPath;
	
	@Value("${zookeeper.safe.permiss.open}")
	public boolean permissOpen = false;
	
	@RequestMapping("/create")
	public String create(Model model, String parent, String name, String data) {
		String cxnstr = getCxnstr();
		if (StringUtils.isBlank(cxnstr)) {
			return "redirect:/";
		}
		parent = ZkCom.getZkPath(parent);
		Zk zk = new Zk(cxnstr);
		String path = "/".equals(parent)?(parent + name):(parent + "/" + name);
		zk.create(path, data.getBytes(Constants.defualtCharset), permissOpen);
		ZkCacheUtil.waitExistsSleep((MyZkClient)zk.getClient(), path);
		return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr+"&path=" + path;
	}
	
	@RequestMapping("/upload")
	public String upload(MultipartHttpServletRequest req, Model model, String path) {
		String cxnstr = getCxnstr();
		if (StringUtils.isBlank(cxnstr)) {
			return "redirect:/";
		}
		path = ZkCom.getZkPath(path);
		HttpSession session = req.getSession();
		// 上传文件保存数据
		String sessionId = session.getId();
		List<MultipartFile> files = req.getFiles("file");
		
		InputStream in = null;
		try {
			in = files.get(0).getInputStream();
		} catch (IOException e) {
			e.printStackTrace();
		}
		String fileName = files.get(0).getOriginalFilename();
		if(StringUtils.isEmpty(fileName)) {
			BaseMsg baseMsg = new BaseMsg();
			baseMsg.addErrMsg("未选择要上传的文件！");
			session.setAttribute("baseMsg", baseMsg);
			return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr+"&path=" + path;
		}
		
		OutputStream out = null;

		String floderPath = Constants.uploadDir + "/" + sessionId + "/";
		File floder = new File(floderPath);
		if (!floder.exists()) {
			floder.mkdirs();
		}
		String filePath = floderPath + fileName;
		File tempFile = new File(filePath);
		try {
			out = new FileOutputStream(tempFile);
			// 保存上传来的文件
			int length = 0;
			byte[] buffer = new byte[1024];
			while ((length = in.read(buffer)) != -1) {
				out.write(buffer, 0, length);
			}
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (out != null)
				try {
					out.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		Zk zk = new Zk(cxnstr);
		
		boolean uploadFlg = false;
		
		// 解压文件
		CompressUtil decompression = new CompressUtil();
		if (fileName.endsWith(".rar") || fileName.endsWith(".zip")) {
			String ext = fileName.substring(fileName.lastIndexOf(".") + 1);
			File filef = new File(filePath.substring(0, filePath.lastIndexOf(".")));
			if (filef.exists()) {
				ComUtil.deleteFolder(filef);
			}
			if("rar".equals(ext)){
				decompression.unRarFile(filePath, filef.getAbsolutePath());
				uploadFlg = ZkOperate.uploadFile(zk.getClient().getZooKeeper(), filef, path);
			} else {
				try {
					decompression.unzip(filePath, filef.getAbsolutePath());
					uploadFlg = ZkOperate.uploadFile(zk.getClient().getZooKeeper(), filef, path);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			String[] listFileName = filef.list();
			if(uploadFlg && listFileName != null && listFileName.length > 0) {
		        ZkCacheUtil.waitExistsSleep((MyZkClient)zk.getClient(), 
		        		ZkCom.getZkPath(path+"/"+listFileName[listFileName.length-1]));
			}
		} else {
			uploadFlg = ZkOperate.uploadFile(zk.getClient().getZooKeeper(), tempFile, path);
			if(uploadFlg) {
                ZkCacheUtil.waitExistsSleep((MyZkClient)zk.getClient(), 
                		ZkCom.getZkPath(path+"/"+tempFile.getName()));
            }
		}
		
		if(!uploadFlg) {
			BaseMsg baseMsg = new BaseMsg();
			baseMsg.addErrMsg("文件上传失败。");
			session.setAttribute("baseMsg", baseMsg);
		}
		ComUtil.deleteFolder(floder);
		
		logger.info("upload, cxnstr:{}, path:{}", cxnstr, path);
		return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr+"&path=" + path;
	}
	
	@RequestMapping("/download")
	public String download(HttpServletRequest req, HttpServletResponse response, Model model, String path) {
		String cxnstr = getCxnstr();
		Response res = new Response();
		int state = -1;
		HttpSession session = req.getSession();
		BaseMsg baseMsg = new BaseMsg();
		if (StringUtils.isBlank(cxnstr)) {
			return "redirect:/";
		}
		path = StringUtils.isBlank(path) ? SEPARATOR : StringUtils.trimToEmpty(path);
		String sessionId = session.getId();

		String floderPath = Constants.downloadDir + "/" + sessionId + "/";
		File floder = new File(floderPath);
		if (floder.exists()) {
			ComUtil.deleteFolder(floder);
		}
		
		if(!floder.mkdirs()) {
			baseMsg.addErrMsg("创建下载目录失败。floder="+floder.getAbsolutePath());
			session.setAttribute("baseMsg", baseMsg);
			return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr+"&path=" + path;
		}
		
		Zk zk = new Zk(cxnstr);
		File newfloder = null;
		if("/".equals(path)) {
			newfloder = new File(floderPath+"ROOT");
		} else {
			newfloder = floder;
		}
		boolean downloadFlg = ZkOperate.downloadFile(zk.getClient().getZooKeeper(), newfloder, path);
		
		File[] files = floder.listFiles();
		File downloadFile = null;
		if(downloadFlg && files != null) {
			if(files.length == 1) {
				downloadFile = files[0];
			} else {
				long time = 0;
				for(File f:files) {
					if(f.lastModified() > time) {
						time = f.lastModified();
						downloadFile = f;
					}
				}
			}
		}
		File file = null;
		if(downloadFile != null) {
			if(downloadFile.isFile()) {
				file = downloadFile;
			} else {
				String fileName = downloadFile.getAbsolutePath()+".zip";
				CompressUtil decompression = new CompressUtil();
				try {
					decompression.zip(downloadFile,fileName);
					file = new File(fileName);
				} catch (IOException e) {
					e.printStackTrace();
					logger.error("压缩文件失败，"+e.getMessage()+", file="+files[0].getAbsolutePath());
				}
			}
		}
		
		if(file != null) {
			BufferedInputStream br;
			try {
				br = new BufferedInputStream(new FileInputStream(file));
		        byte[] buf = new byte[1024];
		        int len = 0;
		        response.reset(); // 非常重要
//		        response.setCharacterEncoding("utf-8");
		        response.setContentType("application/x-msdownload");
	            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
	            OutputStream out = response.getOutputStream();
	            while ((len = br.read(buf)) > 0)
	                out.write(buf, 0, len);
	            br.close();
	            out.close();
	            downloadFlg = true;
			} catch (Exception e) {
				e.printStackTrace();
				logger.error(e.getMessage());
				res.setState(state);
				res.setMsg("下载失败，失败信息："+e.getMessage());
				downloadFlg = false;
			}
		} else {
			downloadFlg = false;
		}
		ComUtil.deleteFolder(floder);
		if(!downloadFlg) {
			baseMsg.addErrMsg("文件下载失败。");
			session.setAttribute("baseMsg", baseMsg);
			logger.info("download, cxnstr:{}, path:{}", cxnstr, path);
			return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr+"&path=" + path;
		}
		return null;
	}
	
	@RequestMapping("/createNode")
	public void createNode(HttpServletResponse response, String parent, String name) {
		String cxnstr = getCxnstr();
		Response res = new Response();
		int state = -1;
		if (StringUtils.isBlank(cxnstr)) {
			res.setState(state);
			res.setMsg("登录超时！");
			writeResponse(response, res);
			return;
		}
		parent = ZkCom.getZkPath(parent);
		Zk zk = new Zk(cxnstr);
		String path = "/".equals(parent)?(parent + name):(parent + "/" + name);
		try {
			zk.create(path, null, permissOpen);
			ZkCacheUtil.waitExistsSleep((MyZkClient)zk.getClient(), path);
		} catch (Exception e) {
			logger.error(e.getMessage());
			res.setState(state);
			res.setMsg("创建失败，失败信息："+e.getMessage());
		}
		logger.info("createNode, cxnstr:{}, path:{}, name:{}", cxnstr, path, name);
		writeResponse(response, res);
	}

	@RequestMapping("/edit")
	public String edit(Model model, String path, String data) {
		String cxnstr = getCxnstr();
		if (StringUtils.isBlank(cxnstr)) {
			return "redirect:/";
		}
		path = ZkCom.getZkPath(path);
		Zk zk = new Zk(cxnstr);
		zk.edit(path, data.getBytes());
		ZkCacheUtil.waitEditSleep((MyZkClient)zk.getClient(), path);
		return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr+"&path=" + path;
	}

	@RequestMapping("/delete")
	public String delete(HttpServletRequest req, Model model, String path, String data) {
		String cxnstr = getCxnstr();
		if (StringUtils.isBlank(cxnstr)) {
			return "redirect:/";
		}
		path = ZkCom.getZkPath(path);
		if("/".equals(path)) {
			BaseMsg baseMsg = new BaseMsg();
			baseMsg.addErrMsg("不允许删除根节点！");
			req.getSession().setAttribute("baseMsg", baseMsg);
			return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr;
		}
		Zk zk = new Zk(cxnstr);
		zk.delete(path);
		ZkCacheUtil.waitNoExistsSleep((MyZkClient)zk.getClient(), path);
		
		int index = path.lastIndexOf("/");
		String parent = ZkCom.getZkPath(path.substring(0, index));
		return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr+"&path=" + parent;
	}

	@RequestMapping("/rmrdel")
	public String rmrdel(HttpServletRequest req, Model model, String path, String data) {
		String cxnstr = getCxnstr();
		if (StringUtils.isBlank(cxnstr)) {
			return "redirect:/";
		}
		path = ZkCom.getZkPath(path);
		if("/".equals(path)) {
			BaseMsg baseMsg = new BaseMsg();
			baseMsg.addErrMsg("不允许删除根节点！");
			req.getSession().setAttribute("baseMsg", baseMsg);
			return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr;
		}
		Zk zk = new Zk(cxnstr);
		zk.deleteRecursive(path);
		ZkCacheUtil.waitNoExistsSleep((MyZkClient)zk.getClient(), path);
		logger.info("deleteRecursive, cxnstr:{}, path:{}", cxnstr, path);
		return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr+"&path=" + StringUtils.substring(path, 0, StringUtils.lastIndexOf(path, "/"));
	}
	
	@RequestMapping("/deleteNode")
	public void deleteNode(HttpServletResponse response, String path) {
		String cxnstr = getCxnstr();
		Response res = new Response();
		int state = -1;
		if (StringUtils.isBlank(cxnstr)) {
			res.setState(state);
			res.setMsg("登录超时！");
			writeResponse(response, res);
			return;
		}
		path = ZkCom.getZkPath(path);
		if("/".equals(path)) {
			res.setState(state);
			res.setMsg("不允许删除根节点！");
			writeResponse(response, res);
			return;
		}
		Zk zk = new Zk(cxnstr);
		try {
			zk.deleteRecursive(path);
			ZkCacheUtil.waitNoExistsSleep((MyZkClient)zk.getClient(), path);
		} catch(Exception e) {
			logger.error(e.getMessage());
			res.setState(state);
			res.setMsg("删除失败，失败信息："+e.getMessage());
			writeResponse(response, res);
			return;
		}
		logger.info("deleteNode, cxnstr:{}, path:{}", cxnstr, path);
		writeResponse(response, res);
	}
	
	@RequestMapping("/renameNode")
	public void renameNode(HttpServletResponse response, String oldPath, String newPath) {
		String cxnstr = getCxnstr();
		Response res = new Response();
		int state = -1;
		if (StringUtils.isBlank(cxnstr)) {
			res.setState(state);
			res.setMsg("登录超时！");
			writeResponse(response, res);
			return;
		}
		oldPath = ZkCom.getZkPath(oldPath);
		newPath = ZkCom.getZkPath(newPath);
		if("/".equals(oldPath) || "/".equals(newPath)) {
			res.setState(state);
			res.setMsg("根目录不可重命名");
			writeResponse(response, res);
		}
		Zk zk = new Zk(cxnstr);
		try {
			if(!ZkOperate.renameNode(zk.getClient().getZooKeeper(), oldPath, newPath)) {
				res.setState(state);
				res.setMsg("重命名失败。");
				writeResponse(response, res);
				return;
			}
			ZkCacheUtil.waitNoExistsSleep((MyZkClient)zk.getClient(), oldPath);
			ZkCacheUtil.waitExistsSleep((MyZkClient)zk.getClient(), newPath);
		} catch (Exception e) {
			logger.error(e.getMessage());
			res.setState(state);
			res.setMsg("重命名失败，失败信息："+e.getMessage());
		}
		logger.info("renameNode, cxnstr:{}, oldPath:{}, newPath:{}", cxnstr, oldPath, newPath);
		writeResponse(response, res);
	}
	
	@RequestMapping("/copyPasteNode")
	public void copyPasteNode(HttpServletResponse response, String path, String targetPath) {
		String cxnstr = getCxnstr();
		Response res = new Response();
		int state = -1;
		if (StringUtils.isBlank(cxnstr)) {
			res.setState(state);
			res.setMsg("登录超时！");
			writeResponse(response, res);
			return;
		}
		path = ZkCom.getZkPath(path);
		targetPath = ZkCom.getZkPath(targetPath);
		if("/".equals(path)) {
			res.setState(state);
			res.setMsg("根目录不可复制粘贴");
			writeResponse(response, res);
			return;
		}
		Zk zk = new Zk(cxnstr);
		try {
			if(!ZkOperate.copyPasteNode(zk.getClient().getZooKeeper(), path, targetPath)) {
				res.setState(state);
				res.setMsg("复制失败，请确认是否已存在同名文件。");
				writeResponse(response, res);
				return;
			}
			int index = path.lastIndexOf("/");
			if(index >= 0) {
			    String name = path.substring(index);
	            ZkCacheUtil.waitExistsSleep((MyZkClient)zk.getClient(), ZkCom.getZkPath(targetPath+name));
			}
		} catch (Exception e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			res.setState(state);
			res.setMsg("复制失败，失败信息："+e.getMessage());
		}
		logger.info("copyPasteNode, cxnstr:{}, path:{}, targetPath:{}", cxnstr, path, targetPath);
		writeResponse(response, res);
	}
	
	@RequestMapping("/cutPasteNode")
	public void cutPasteNode(HttpServletResponse response, String path, String targetPath) {
		String cxnstr = getCxnstr();
		Response res = new Response();
		int state = -1;
		if (StringUtils.isBlank(cxnstr)) {
			res.setState(state);
			res.setMsg("登录超时！");
			writeResponse(response, res);
			return;
		}
		path = ZkCom.getZkPath(path);
		targetPath = ZkCom.getZkPath(targetPath);
		if("/".equals(path)) {
			res.setState(state);
			res.setMsg("根目录不可剪切粘贴");
			writeResponse(response, res);
			return;
		}
		Zk zk = new Zk(cxnstr);
		try {
			if(!ZkOperate.cutPasteNode(zk.getClient().getZooKeeper(), path, targetPath)) {
				res.setState(state);
				res.setMsg("移动或剪切粘贴失败，请确认是否已存在同名文件。");
				writeResponse(response, res);
				return;
			}
			ZkCacheUtil.waitNoExistsSleep((MyZkClient)zk.getClient(), path);
			int index = path.lastIndexOf("/");
            if(index >= 0) {
                String name = path.substring(index);
                ZkCacheUtil.waitExistsSleep((MyZkClient)zk.getClient(), ZkCom.getZkPath(targetPath+name));
            }
		} catch (Exception e) {
			logger.error(e.getMessage());
			res.setState(state);
			res.setMsg("移动或剪切粘贴失败，失败信息："+e.getMessage());
		}
		logger.info("cutPasteNode, cxnstr:{}, path:{}, targetPath:{}", cxnstr, path, targetPath);
		writeResponse(response, res);
	}
	
	@RequestMapping("/backup")
	public String backup(HttpServletRequest request, Model model, String path, String fileName) {
		String cxnstr = getCxnstr();
		HttpSession session = request.getSession();
		BaseMsg baseMsg = new BaseMsg();
		if (StringUtils.isBlank(cxnstr)) {
			return "redirect:/";
		}
		if (StringUtils.isBlank(path)) {
			baseMsg.addErrMsg("zookeeper备份路径不能为空！");
			session.setAttribute("baseMsg", baseMsg);
			return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr;
		}
		path = ZkCom.getZkPath(path);
		String procjetName = "";
		if ("/".equals(path)) {
			procjetName = "ROOT";
		} else {
			procjetName = path.substring(path.lastIndexOf("/")+1);
		}
		if(ComUtil.isEmpty(fileName)) {
			SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
			fileName = procjetName+"_" + format.format(new Date());
		}
		
		File filedir = new File(backupDirPath);
		if(!filedir.exists()) {
			if(!filedir.mkdirs()) {
				baseMsg.addErrMsg("创建备份目录失败！backupDirPath="+filedir.getAbsolutePath());
				session.setAttribute("baseMsg", baseMsg);
				return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr;
			}
		}

		File file = new File(backupDirPath, fileName);
		if(file.exists()) {
			baseMsg.addErrMsg("备份文件已存在！");
			session.setAttribute("baseMsg", baseMsg);
			return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr;
		}
		
		Zk zk = new Zk(cxnstr);
		try {
			if(!ZkOperate.backup(zk.getClient().getZooKeeper(), path, file)) {
				baseMsg.addErrMsg("数据备份失败。");
			}
		} catch (Exception e) {
			logger.error(e.getMessage());
			baseMsg.addErrMsg("数据备份失败，失败信息："+e.getMessage());
		}
		session.setAttribute("baseMsg", baseMsg);
		logger.info("backup, cxnstr:{}, path:{}", cxnstr, path);
		return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr+"&path=" + path;
	}
	
	@RequestMapping("/recover")
	public String recover(HttpServletRequest request, Model model, String fileName) {
		String cxnstr = getCxnstr();
		BaseMsg baseMsg = new BaseMsg();
		HttpSession session = request.getSession();
		if (StringUtils.isBlank(cxnstr)) {
			return "redirect:/";
		}
		if (StringUtils.isBlank(fileName)) {
			baseMsg.addErrMsg("备份文件不能为空！");
			session.setAttribute("baseMsg", baseMsg);
			return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr;
		}
		
		Zk zk = new Zk(cxnstr);
		File file = new File(backupDirPath, fileName);
		if (!file.exists()) {
			baseMsg.addErrMsg("备份文件不存在！path="+file.getAbsolutePath());
			session.setAttribute("baseMsg", baseMsg);
			return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr;
		}
		try {
			if (!ZkOperate.recover(zk.getClient().getZooKeeper(), file.getAbsolutePath())) {
				baseMsg.addErrMsg("数据还原失败。");
			}
			MyZkClient client = (MyZkClient)zk.getClient();
			if(client.useCache) client.zkData.clear();
		} catch (Exception e) {
			logger.error(e.getMessage());
			baseMsg.addErrMsg("数据还原失败，失败信息："+e.getMessage());
		}
		session.setAttribute("baseMsg", baseMsg);
		logger.info("recover, cxnstr:{}, fileName:{}", cxnstr, fileName);
		return "redirect:/read/addr?"+Constants.CX_STR+"="+cxnstr;
	}
	
	@RequestMapping("/deleteBackupFile")
	public void deleteBackupFile(HttpServletResponse response, String fileName) {
		String cxnstr = getCxnstr();
		Response res = new Response();
		int state = -1;
		if (StringUtils.isBlank(cxnstr)) {
			res.setState(state);
			res.setMsg("登录超时！");
			writeResponse(response, res);
			return;
		}
		if (StringUtils.isBlank(fileName)) {
			res.setState(state);
			res.setMsg("取不到文件名！");
			writeResponse(response, res);
			return;
		}
		File file = new File(backupDirPath, fileName);
		if (!file.exists()) {
			res.setState(state);
			res.setMsg("备份文件不存在！path="+file.getAbsolutePath());
			writeResponse(response, res);
			return;
		}
		if (ComUtil.deleteFolder(file)) {
			logger.info("deleteBackupFile, cxnstr:{}, fileName:{}", cxnstr, fileName);
			writeResponse(response, res);
		} else {
			res.setState(state);
			res.setMsg("删除备份文件失败！path="+file.getAbsolutePath());
			writeResponse(response, res);
		}
	}

	private String getCxnstr() {
		HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		return (String) req.getSession().getAttribute(Constants.CX_STR);
	}
	
	private void writeResponse(HttpServletResponse response, Response res) {
		response.setContentType("application/json;charset=UTF-8");
		try {
			response.getWriter().print(gson.toJson(res));
		} catch (IOException e) {
			e.printStackTrace();
			logger.error(e.getMessage());
		}
	}
}
